using System;
using System.Data;
using System.Collections.Generic;

using Axiom;
using Axiom.Core;
using Axiom.Graphics;
using Axiom.Configuration;
using Axiom.Math;
using Axiom.Overlays;
using Axiom.Input;

namespace Game
{


	public class AxiomEngineWrapper
	{

		#region Private fields
        private string configFile = "EngineConfigUnix.xml";
        private string logFile = "AxiomExample.log";
        private long lastOverlayUpdate = -1000;
        private float moveScale = 0, rotScale = 0, moveSpeed = 100, rotateSpeed = 36;
        private Vector2 rotateVector = new Vector2(0, 0);
        private Vector3 translateVector = new Vector3(0, 0, 0);
        private Root root;
        private Camera camera;
        private SceneManager sceneManager;
        private RenderWindow renderWindow;
        private InputReader inputReader;
        #endregion Private fields

        #region Protected properties
        /// <summary>
        /// Root (Axiom.Core.Root)
        /// </summary>
        protected Root Root
        {
            get
            {
                return root;
            }
            set
            {
                root = value;
            }
        }

        /// <summary>
        /// Camera (Axiom.Core.Camera)
        /// </summary>
        protected Camera Camera
        {
            get
            {
                return camera;
            }
            set
            {
                camera = value;
            }
        }

        /// <summary>
        /// SceneManager (Axiom.Core.SceneManager)
        /// </summary>
        protected SceneManager SceneManager
        {
            get
            {
                return sceneManager;
            }
            set
            {
                sceneManager = value;
            }
        }


        /// <summary>
        /// RenderWindow (Axiom.Graphics.RenderWindow)
        /// </summary>
        protected RenderWindow RenderWindow
        {
            get
            {
                return renderWindow;
            }
            set
            {
                renderWindow = value;
            }
        }

        /// <summary>
        /// InputReader (Axiom.Input.InputReader)
        /// </summary>
        protected InputReader InputReader
        {
            get
            {
                return inputReader;
            }
            set
            {
                inputReader = value;
            }
        }
        #endregion Protected fields

        #region Public properties
        /// <summary>
        /// Gets or set the config file name and path
        /// </summary>
        public string ConfigFile
        {
            get
            {
                return configFile;
            }
            set
            {
                configFile = value;
            }
        }

        /// <summary>
        /// Gets or sets the config file name and path
        /// </summary>
        public string LogFile
        {
            get
            {
                return logFile;
            }
            set
            {
                logFile = value;
            }
        }
        #endregion
		
		
		 #region Init methods
        /// <summary>
        /// Starts the example
        /// </summary>
        public virtual void Run()
        {
            try
            {
                if (Setup())
                    root.StartRendering();
            }
            catch (System.Reflection.ReflectionTypeLoadException ex)
            {
                // This catches directx missing (or too old) to log :)
                for (int i = 0; i < ex.LoaderExceptions.Length; i++)
                    if (LogManager.Instance != null)
                        LogManager.Instance.Write(ex.LoaderExceptions[i].Message);
            }
            catch (Exception ex)
            {
                if (LogManager.Instance != null)
                    LogManager.Instance.Write(ex.ToString());
            }

            // TODO: Memory cleanup here..
        }

        /// <summary>
        /// Initalizes the application
        /// </summary>
        /// <returns>True if successfull, False to exit the application</returns>
        protected virtual bool Setup()
        {
            root = new Root(ConfigFile, LogFile);

            SetupResources();

            // Run config utility, exit program if it returns false
            if (!Configure())
                return false;
            else
                renderWindow = Root.Instance.Initialize(true);

            // Initalize input
            inputReader = PlatformManager.Instance.CreateInputReader();
            inputReader.Initialize(renderWindow, true, true, false, false);

            ChooseSceneManager();
            CreateCamera();
            CreateViewports();

            // Set default mipmap level (NB some APIs ignore this)
            TextureManager.Instance.DefaultNumMipMaps = 5;

            // Create any resource listeners (for loading screens)
            CreateResourceListener();

            // Add some event handlers
            RegisterEventHandlers();

            // Lastly, create the scene
            CreateScene();

            return true;
        }

        /// <summary>
        /// Adds the searchpaths from "EngineConfig.xml" to resources
        /// </summary>
        protected virtual void SetupResources()
        {
            EngineConfig config = new EngineConfig();

            config.ReadXml(ConfigFile);

            foreach (EngineConfig.FilePathRow row in config.FilePath)
                ResourceManager.AddCommonArchive(row.src, row.type);
        }

        /// <summary>
        /// Configures the application
        /// </summary>
        /// <returns>True if successfull, False to exit the application</returns>
        protected virtual bool Configure()
        {
//            ConfigureConsole cc = new ConfigureConsole();
//            if (cc.Show() == ConfigureConsole.DialogResult.Exit)
//                return false;
//
//            // Set selected rendersystem
//            root.RenderSystem = cc.RenderSystem;
			
			root.RenderSystem = root.RenderSystems[0];

            return true;
        }

        /// <summary>
        /// Chooses scene manager (SceneType.Generic)
        /// </summary>
        protected virtual void ChooseSceneManager()
        {
            // Create a generic scene manager
            sceneManager = root.SceneManagers.GetSceneManager(SceneType.ExteriorClose);
        }

        /// <summary>
        /// Creates a camera at 500 in the Z direction that looks at -300 in the Z direction
        /// </summary>
        protected virtual void CreateCamera()
        {
            // Create the camera
            camera = sceneManager.CreateCamera("PlayerCam");

            // Position it at 500 in the Z direction
            camera.Position = new Vector3(0, 0, 500);

            // Look back along -Z
            camera.LookAt(new Vector3(0, 0, -300));
            camera.Near = 5;
        }

        /// <summary>
        /// Creates a viewport using mCamera
        /// </summary>
        protected virtual void CreateViewports()
        {
            // Create one viewport, entire window
            Viewport vp = renderWindow.AddViewport(camera);
            vp.BackgroundColor = ColorEx.Black;

            // Alter the camera aspect ratio to match the viewport
            camera.AspectRatio = vp.ActualWidth / vp.ActualHeight;
        }

        /// <summary>
        /// Optional override method where you can create resource listeners (e.g. for loading screens)
        /// </summary>
        protected virtual void CreateResourceListener()
        {

        }

        /// <summary>
        /// Registers event handlers and calls InitOverlay()
        /// </summary>
        protected virtual void RegisterEventHandlers()
        {
            root.FrameStarted += UpdateInput;
            root.FrameStarted += UpdateOverlay;
            root.FrameStarted += FrameStarted;
            root.FrameEnded += FrameEnded;

            // Create debug overlay
            InitOverlay();
        }

        /// <summary>
        /// Initalizes the debug overlay (fps, etc..)
        /// </summary>
        protected virtual void InitOverlay()
        {
            Overlay o = OverlayManager.Instance.GetByName("Core/DebugOverlay");
            if (o == null)
                throw new Exception("Could not find overlay named 'Core/DebugOverlay'.");
            o.Show();
        }

        /// <summary>
        /// Creates the scene
        /// </summary>
        protected virtual void CreateScene()
        {

        }
        #endregion Init methods

        #region Event handlers
        /// <summary>
        /// This is run before each frame
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        protected virtual void FrameStarted(object source, FrameEventArgs e)
        {

        }

        /// <summary>
        /// This is run after each frame
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        protected virtual void FrameEnded(object source, FrameEventArgs e)
        {

        }

        /// <summary>
        /// Checks for input and handles it
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        protected virtual void UpdateInput(object source, FrameEventArgs e)
        {
            inputReader.Capture();

            #region Camera movement
            // Reset vectors
            rotateVector.x = translateVector.x = 0;
            rotateVector.y = translateVector.y = 0;
            translateVector.z = 0;

            // Move
            moveScale = moveSpeed * e.TimeSinceLastFrame;

            // Rotate
            rotScale = rotateSpeed * e.TimeSinceLastFrame;

            // Move forward and back
            if (inputReader.IsKeyPressed(KeyCodes.W) || inputReader.IsKeyPressed(KeyCodes.Up))
                translateVector.z = -moveScale;
            else if (inputReader.IsKeyPressed(KeyCodes.S) || inputReader.IsKeyPressed(KeyCodes.Down))
                translateVector.z = moveScale;

            // Move left and right
            if (inputReader.IsKeyPressed(KeyCodes.A))
                translateVector.x = -moveScale;
            else if (inputReader.IsKeyPressed(KeyCodes.D))
                translateVector.x = moveScale;

            // Move up and down
            if (inputReader.IsKeyPressed(KeyCodes.PageUp))
                translateVector.y = moveScale;
            else if (inputReader.IsKeyPressed(KeyCodes.PageDown))
                translateVector.y = -moveScale;

            // Rotate left and right
            if (inputReader.IsKeyPressed(KeyCodes.Left))
                rotateVector.x = -rotScale;
            else if (inputReader.IsKeyPressed(KeyCodes.Right))
                rotateVector.x = rotScale;

            // Right mouse button pressed
            if (inputReader.IsMousePressed(MouseButtons.Right))
            {
                // Translate
                translateVector.x += inputReader.RelativeMouseX * 0.13f;
                translateVector.y -= inputReader.RelativeMouseY * 0.13f;
            }
            else
            {
                // Apply mouse rotation
                rotateVector.x += inputReader.RelativeMouseX * 0.13f;
                rotateVector.y += inputReader.RelativeMouseY * 0.13f;
            }

            // Apply changes
            camera.Yaw(-rotateVector.x);
            camera.Pitch(-rotateVector.y);
            camera.MoveRelative(translateVector);
            #endregion Camera movement

            // TODO: what about window-closing-event?
            if (inputReader.IsKeyPressed(KeyCodes.Escape))
            {
                Root.Instance.QueueEndRendering();

                // TODO: Find a better way
                if (root != null)
                {
                    // remove event handlers
                    //  engine.FrameStarted -= new FrameEvent( OnFrameStarted );
                    // engine.FrameEnded -= new FrameEvent( OnFrameEnded );

                    root.Dispose();
                }

                sceneManager.RemoveAllCameras();
                sceneManager.RemoveCamera(camera);
                camera = null;
                Root.Instance.RenderSystem.DetachRenderTarget(renderWindow);
                renderWindow.Dispose();
                return;
            }

        }

        /// <summary>
        /// Updates the debug overlay
        /// </summary>
        protected virtual void UpdateOverlay(object source, FrameEventArgs e)
        {
            if (Root.Instance.Timer.Milliseconds - lastOverlayUpdate >= 1000)
            {
                lastOverlayUpdate = Root.Instance.Timer.Milliseconds;

                //OverlayElementManager overlayManager = OverlayElementManager.;

                OverlayElement element =
                        OverlayElementManager.Instance.GetElement("Core/DebugText");
                element.Text = renderWindow.DebugText;

                element = OverlayElementManager.Instance.GetElement("Core/CurrFps");
                element.Text = string.Format("Current FPS: {0}", Root.Instance.CurrentFPS);

                element = OverlayElementManager.Instance.GetElement("Core/BestFps");
                element.Text = string.Format("Best FPS: {0}", Root.Instance.BestFPS);

                element = OverlayElementManager.Instance.GetElement("Core/WorstFps");
                element.Text = string.Format("Worst FPS: {0}", Root.Instance.WorstFPS);

                element = OverlayElementManager.Instance.GetElement("Core/AverageFps");
                element.Text = string.Format("Average FPS: {0}", Root.Instance.AverageFPS);

                element = OverlayElementManager.Instance.GetElement("Core/NumTris");
                element.Text = string.Format("Triangle Count: {0}", sceneManager.TargetRenderSystem.FacesRendered);
            }
        }
        #endregion Events
	}
}
